#include "config.h"

#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <math.h>

#define DBG_SUBSYS S_LIBSTORAGE

#include "limits.h"
#include "adt.h"
#include "ynet_rpc.h"
#include "sysy_lib.h"
#include "cluster.h"
#include "chunk.h"
#include "md_map.h"
#include "bmap.h"
#include "net_global.h"
#include "lich_md.h"
#include "squeue.h"
#include "../chunk/chunk_proto.h"
#include "../replica/replica.h"
#include "timer.h"
#include "volume_proto.h"
#include "lich_qos.h"

/*
 * TODO
 * iops bw bucket yield by caculate, rather than polling
 */

#define POLLING_INVERVAL       (50)
#define RECOVERY_QOS_INTERVAL  (6 * 1000 * 1000)
#define QOS_BURST_RATIO        (1.1)

static inline double get_burst_ratio() {
        return 1.0 + qos_config_get_iops_jitter() / 100.0;
}

// IOPS


int __throt_iops_request(token_bucket_t *bucket, suseconds_t *delay)
{
        int ret, is_ready;

        if (likely(!bucket->inited)) {
                goto out;
        }

        ret = token_bucket_consume(bucket, 1, &is_ready, NULL);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        if (unlikely(!is_ready)) {
                *delay = POLLING_INVERVAL;
                ret = ECANCELED;
                goto err_ret;
        }

out:
        return 0;
err_ret:
        return ret;
}

static int __throt_bw_request(token_bucket_t *bucket, suseconds_t *delay, size_t size)
{
        int ret, is_ready;

        if (likely(!bucket->inited)) {
                goto out;
        }

        ret = token_bucket_consume(bucket, size, &is_ready, NULL);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        if (!is_ready) {
                *delay = POLLING_INVERVAL;
                ret = ECANCELED;
                goto err_ret;
        }
out:
        return 0;
err_ret:
        return ret;
}

static LLU str2val(const char *str)
{
        LLU val;

        val = atoll(str);

        while (*str) {
                switch (*str) {
                        case 'k':
                        case 'K':
                                val *= 1024;
                                break;
                        default:
                                break;
                }

                str += 1;
        }

        return val;
}

/*
 * type 1: lich_system_attr_iops fill_rate
 * type 2: lich_system_attr_iops fill_rate,burst_max
 */
int throt_attr_decode(const char *buf, uint64_t *rate, uint64_t *burst_max)
{
        int ret, count;
        uint64_t _rate, _tmp, _burst_max;
        char *list[2], tmp[MAX_INFO_LEN];

        *rate = 0;
        *burst_max = 0;

        count = 2;
        YASSERT(strlen(buf) < MAX_INFO_LEN);
        strcpy(tmp, buf);

        _str_split(tmp, ',', list, &count);
        if (count != 1 && count != 2) {
                ret = EINVAL;
                GOTO(err_ret, ret);
        }

        _rate = str2val(list[0]);
        if (_rate <= 0) {
                ret = EINVAL;
                GOTO(err_ret, ret);
        }

        if (count == 1) {
                _burst_max = (uint64_t)(_rate * get_burst_ratio());
        } else if(count == 2) {
                _tmp = str2val(list[1]);
                if (_tmp < 0) {
                        ret = EINVAL;
                        GOTO(err_ret, ret);
                }
                _burst_max = _max(_tmp, _rate);
        } else {
                DWARN("shoule not be run here!\n");
                ret = EINVAL;
                GOTO(err_ret, ret);
        }

        if (_burst_max <= 0) {
                ret = EINVAL;
                GOTO(err_ret, ret);
        }

        *rate = _rate;
        *burst_max = _burst_max;
        YASSERT(*burst_max >= *rate && *rate > 0);

        return 0;
err_ret:
        return ret;
}

static int __throt_set(token_bucket_t *bucket, const char *_buf)
{
        int ret;
        uint64_t rate, burst_max;

        ret = throt_attr_decode(_buf, &rate, &burst_max);
        if (unlikely(ret)) {
                DWARN("attr decode error %s\n", _buf);
                goto err_destory;
        }

        DINFO("throt %ju %ju\n", rate, burst_max);
        token_bucket_set(bucket, "throt_set", rate, rate, burst_max, 1, 1);

        return 0;

err_destory:
        token_bucket_destroy(bucket);
        return 0;
}

int throt_qos_set(token_bucket_t *bucket, const char *_buf) {
        return __throt_set(bucket, _buf);
}

int throt_bw_set(token_bucket_t *bucket, const char *_buf) {
        return __throt_set(bucket, _buf);
}

int throt_request(volume_proto_t *volume_proto, const char *name , size_t size)
{
        int ret, trytimes = 0;
        suseconds_t delay;

retry:
        ret = __throt_iops_request(&volume_proto->iops_bucket, &delay);
        if (unlikely(ret)) {
                if (likely(ret == ECANCELED)) {
                        trytimes ++;
                        schedule_sleep(name, delay);
                        goto retry;
                } else {
                        GOTO(err_ret, ret);
                }
        }

retry1:
        ret = __throt_bw_request(&volume_proto->bw_bucket, &delay, size);
        if (unlikely(ret)) {
                if (likely(ret == ECANCELED)) {
                        trytimes ++;
                        schedule_sleep(name, delay);
                        goto retry1;
                } else {
                        GOTO(err_ret, ret);
                }
        }

        if (unlikely(trytimes >= 5000))
                DWARN("throt_request try %d\n", trytimes);

        return 0;
err_ret:
        return ret;
}


int volume_proto_throt_request(volume_proto_t *volume_proto, const char *name, size_t size)
{
        int ret;

        ret = throt_request(volume_proto, name, size);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        return 0;
err_ret:
        return ret;
}

/**
 * new QoS
 *
 */

int recovery_load_scale(const char *pool);
int recovery_load_task_max(const char *pool);

#if 0

STATIC int __check_scale_relationship(volume_proto_t *volume_proto)
{
        int ret = 0;
        qos_t *qos = &volume_proto->qos;
        uint64_t io_delta, r_delta, total;

        // 检查比例关系
        io_delta = qos->io.w2 - qos->io.w1;
        r_delta = qos->recovery.w2 - qos->recovery.w1;
        total = r_delta + io_delta;

        if (total == 0)
                return 0;

        if (qos->scale == -1) {
                qos->scale = recovery_load_scale(volume_proto->table1.pool);
        }

        if (r_delta * 100  < total * qos->scale) {
                ret = -1;
        } else if (r_delta * 100  > total * qos->scale) {
                ret = 1;
        }

        DINFO("vol "CHKID_FORMAT" r %ju io %ju total %ju scale %ju %d ret %d\n",
              CHKID_ARG(&volume_proto->chkid),
              r_delta,
              io_delta,
              total,
              r_delta / total,
              qos->scale,
              ret);

        return ret;
}

STATIC int __qos_wakeup__(volume_proto_t *volume_proto)
{
        qos_t *qos = &volume_proto->qos;

        int cmp = __check_scale_relationship(volume_proto);

        DINFO("vol "CHKID_FORMAT" cmp %d\n",
              CHKID_ARG(&volume_proto->chkid),
              cmp);

        if (cmp < 0) {
                co_cond_signal(&qos->recovery.cond, 0);
        } else if (cmp > 0) {
                co_cond_signal(&qos->io.cond, 0);
        } else {
                co_cond_signal(&qos->io.cond, 0);
                co_cond_signal(&qos->recovery.cond, 0);
        }

        return 0;
}

STATIC int __qos_wakeup(volume_proto_t *volume_proto)
{
        qos_t *qos = &volume_proto->qos;

        DINFO("vol "CHKID_FORMAT" task %ju %ju\n",
              CHKID_ARG(&volume_proto->chkid),
              qos->io.task_count,
              qos->recovery.task_count);

        // TODO 要保证能唤醒所有任务
        if (qos->recovery.task_count == 0) {
                co_cond_signal(&qos->io.cond, 0);
        } else if (qos->io.task_count == 0) {
                co_cond_signal(&qos->recovery.cond, 0);
        } else {
                __qos_wakeup__(volume_proto);
        }

        return 0;
}

STATIC int __qos_dump(volume_proto_t *volume_proto, const char *tip)
{
        qos_t *qos = &volume_proto->qos;

        DBUG("%s vol "CHKID_FORMAT" io %ju %ju %ju %ju r %ju %ju %ju %ju task %ju %d - %ju %d @ %d %d\n",
              tip,
              CHKID_ARG(&volume_proto->chkid),
              qos->io.request_count,
              qos->io.exit_count,
              qos->io.w1,
              qos->io.w2,
              qos->recovery.request_count,
              qos->recovery.exit_count,
              qos->recovery.w1,
              qos->recovery.w2,
              qos->io.task_count,
              qos->io.cond.size,
              qos->recovery.task_count,
              qos->recovery.cond.size,
              qos->task_max,
              qos->scale);

        return 0;
}

int volume_proto_recovery_enter(volume_proto_t *volume_proto)
{
        int ret;
        int64_t interval;
        qos_t *qos = &volume_proto->qos;

        {
                // sliding time window

                _gettimeofday(&qos->t2, NULL);

                interval = _time_used(&qos->t1, &qos->t2);
                if (interval >= 1000 * 1000) {
                        qos->io.w1 = qos->io.w2;
                        qos->recovery.w1 = qos->recovery.w2;
                        qos->t1 = qos->t2;

                        // update config
                        qos->task_max = recovery_load_task_max(volume_proto->table1.pool);
                        qos->scale = recovery_load_scale(volume_proto->table1.pool);
                }
        }

        if (qos->task_max == 0) {
                qos->task_max = recovery_load_task_max(volume_proto->table1.pool);
        }

        while (1) {
                __qos_dump(volume_proto, "recovery_enter");

                if (qos->io.task_count == 0 && qos->io.cond.size == 0) {
                        break;
                }

                if (qos->recovery.task_count == 0)
                        break;

                if (qos->io.task_count + qos->recovery.task_count < qos->task_max) {
                        break;
                }

                if (__check_scale_relationship(volume_proto) <= 0)
                        break;

                DBUG("vol "CHKID_FORMAT" wait\n", CHKID_ARG(&volume_proto->chkid));

                ret = co_cond_wait2(&qos->recovery.cond, "recovery_enter");
                if (unlikely(ret))
                        UNIMPLEMENTED(__DUMP__);

                break;
        }

        qos->recovery.task_count++;
        qos->recovery.request_count++;

        // TODO 需要考虑权重，选择一基本单元
        qos->recovery.w2 += 1;

        return 0;
}

int volume_proto_recovery_exit(volume_proto_t *volume_proto)
{
        qos_t *qos = &volume_proto->qos;

        // __qos_wakeup(volume_proto);

        co_cond_signal(&qos->recovery.cond, 0);

        qos->recovery.task_count--;
        qos->recovery.exit_count++;

        __qos_dump(volume_proto, "recovery_exit");
        return 0;
}

int volume_proto_io_enter(volume_proto_t *volume_proto, const io_t *io)
{
        int ret;
        qos_t *qos = &volume_proto->qos;

        if (qos->task_max == 0) {
                qos->task_max = recovery_load_task_max(volume_proto->table1.pool);
        }

        while (1) {
                __qos_dump(volume_proto, "io_enter");

                // 另一端无任务
                if (qos->recovery.task_count == 0 && qos->recovery.cond.size == 0) {
                        break;
                }

                // 保证总有运行的任务，可以唤醒wait task
                if (qos->io.task_count == 0)
                        break;

                // TODO 如何确定饱和点？
                // 没有达到饱和点
                if (qos->io.task_count + qos->recovery.task_count < qos->task_max) {
                        break;
                }

                // 按比例分配任务
                if (__check_scale_relationship(volume_proto) >= 0)
                        break;

                DBUG("vol "CHKID_FORMAT" wait\n", CHKID_ARG(&volume_proto->chkid));

                ret = co_cond_wait2(&qos->io.cond, "io_enter");
                if (unlikely(ret))
                        UNIMPLEMENTED(__DUMP__);

                break;
        }

        qos->io.task_count++;
        qos->io.request_count++;

        // TODO 如何确定权重？
        if (io->size <= 4096) {
                qos->io.w2 += 1;
        } else {
                qos->io.w2 += 1;
        }

        return 0;
}

int volume_proto_io_exit(volume_proto_t *volume_proto)
{
        qos_t *qos = &volume_proto->qos;

        // __qos_wakeup(volume_proto);

        co_cond_signal(&qos->io.cond, 0);

        qos->io.task_count--;
        qos->io.exit_count++;

        __qos_dump(volume_proto, "io_exit");
        return 0;
}

#else

/**
 * @param qos
 * @param b iops
 * @return
 */
static inline int __qos_set_iops(qos_t *qos, uint64_t iops)
{
        uint64_t rps;

        YASSERT(qos->scale > 0 && qos->scale < 100);

        itorange(&qos->scale, 1, 99);

        int iops_min = qos_config_get_iops_min();
        if (iops < iops_min)
                iops = iops_min;

        if (qos->scale < 50) {
                // why?
                int exp = qos_config_get_iops_exp();
                rps = (uint64_t)pow(iops, 0.01 * exp) * qos->scale / (100 - qos->scale);
        } else {
                rps = (uint64_t)pow(iops, 1.0) * qos->scale / (100 - qos->scale);
        }

        if (rps <= 0)
                rps = 1;

        DINFO("scale %d iops_min %d iops %ju rps %ju\n",
              qos->scale, iops_min, iops, rps);

        qos->range.p2.io.token = iops;
        qos->range.p2.recovery.token = rps;

        double burst = get_burst_ratio();

        token_bucket_set(&qos->io.bucket, "qos_iops", iops, iops, (uint64_t)(burst * iops), 1, 1);
        token_bucket_set(&qos->recovery.bucket, "qos_rps", rps, rps, (uint64_t)(burst * rps), 1, 1);

        return 0;
}

static inline int __qos_change_iops(qos_t *qos, int delta)
{
        uint64_t new_iops;

        if (delta >= 0) {
                new_iops = qos->range.p2.io.token + delta;
        } else {
                // TODO
                if (qos->range.p2.io.token > abs(delta)) {
                        new_iops = qos->range.p2.io.token - abs(delta);
                } else {
                        new_iops = 0;
                }
        }

        DINFO("change iops %ju -> %ju delta %d\n", qos->range.p2.io.token, new_iops, delta);

        return __qos_set_iops(qos, new_iops);
}

/**
 * @pre set qos->is_recovery
 * @pre init bucket
 *
 * @param qos
 * @param is_io
 * @return
 */
static inline int __qos_wait(qos_t *qos, int is_io)
{
        int ret;
        suseconds_t delay;
        token_bucket_t *bucket;

        if (qos->is_recovery) {
                if (!qos_config_get_iops_onoff())
                        goto out;

                bucket = is_io ? &qos->io.bucket : &qos->recovery.bucket;

                YASSERT(bucket->capacity > 0);

                while (1) {
                        // SCHEDULE_LEASE_SET();

                        ret = __throt_iops_request(bucket, &delay);
                        if (unlikely(ret)) {
                                if (ret == ECANCELED) {
                                        schedule_sleep(is_io ? "io_qos" : "r_qos", delay);
                                        continue;
                                } else {
                                        GOTO(err_ret, ret);
                                }
                        }

                        break;
                }
        }

out:
        return 0;
err_ret:
        return ret;
}

static inline int __qos_dump(volume_proto_t *volume_proto, const char *tip)
{
        qos_t *qos = &volume_proto->qos;

        DBUG("%s vol "CHKID_FORMAT" (%p) r %ju %ju %ju wait %ju io %ju %ju %ju %ju task %ju %ju bucket %0.3f %0.3f @ %d mode %d %ju\n",
              tip,
              CHKID_ARG(&volume_proto->chkid),
              volume_proto,
              qos->recovery.enter_count,
              qos->recovery.request_count,
              qos->recovery.exit_count,
              qos->recovery.enter_count - qos->recovery.request_count,
              qos->io.enter_count,
              qos->io.request_count,
              qos->io.exit_count,
              qos->io.enter_count - qos->io.request_count,
              qos->recovery.task_count,
              qos->io.task_count,
              qos->recovery.bucket.capacity,
              qos->io.bucket.capacity,
              qos->scale,
              qos->is_recovery,
              qos->recovery_count);

        return 0;
}

int recovery_qos_init(qos_t *qos)
{
#if 0
        _gettimeofday(&volume_proto->qos.t1, NULL);
        _gettimeofday(&volume_proto->qos.t2, NULL);

        co_cond_init(&volume_proto->qos.io.cond);
        co_cond_init(&volume_proto->qos.recovery.cond);
#endif

        qos->task_max = 0;
        qos->scale = -1;

        timerange2_init(&qos->io_range, "io_range", RECOVERY_QOS_INTERVAL);

        return 0;
}

int volume_proto_recovery_throt_req(volume_proto_t *volume_proto)
{
        int ret;
        qos_t *qos = &volume_proto->qos;
        uint64_t task_wait = qos->recovery.enter_count - qos->recovery.request_count;

        DBUG(""CHKID_FORMAT" recovery enter %ju req %ju wait %ju run %ju\n",
              CHKID_ARG(&volume_proto->chkid),
              qos->recovery.enter_count,
              qos->recovery.request_count,
              task_wait,
              qos->recovery.task_count);

        if (unlikely(task_wait > RECOVERY_WAIT_THROT) || qos->recovery.task_count > RECOVERY_TASK_MAX) {
                ret = EBUSY;
                GOTO(err_ret, ret);
        }

        return 0;
err_ret:
        return ret;
}

/**
 * @todo 无IO的情况
 * @todo 少量IO会同步压低恢复性能
 *
 * @param volume_proto
 * @return
 */
int volume_proto_recovery_enter(volume_proto_t *volume_proto, const chkid_t *chkid)
{
        int ret;
        int64_t interval;
        qos_t *qos = &volume_proto->qos;

        (void) chkid;

        if (qos->scale == -1) {
                qos->scale = recovery_load_scale(volume_proto->table1.pool);
        }

        if (qos->scale <= 0) {
                qos->scale = 1;
        }

        YASSERT(qos->scale > 0);

        // sliding time window
        __qos_dump(volume_proto, "recovery_enter");

        _gettimeofday(&qos->range.p2.t, NULL);
        // qos->range.p2.io.request = qos->io.request_count;
        // qos->range.p2.recovery.request = qos->recovery.request_count;

        interval = _time_used(&qos->range.p1.t, &qos->range.p2.t);
        if (interval >= RECOVERY_QOS_INTERVAL) {
                YASSERT(qos->range.p2.io.request >= qos->range.p1.io.request);
                YASSERT(qos->range.p2.recovery.request >= qos->range.p1.recovery.request);

                uint64_t m = (qos->range.p2.io.request - qos->range.p1.io.request);
                uint64_t n = (qos->range.p2.recovery.request - qos->range.p1.recovery.request);

                uint64_t a2 = m * 1000 * 1000 / interval;
                uint64_t b2 = n * 1000 * 1000 / interval;

                DBUG("vol "CHKID_FORMAT" m %ju n %ju interval %ju a2 %ju b2 %ju mode %d count %ju\n",
                      CHKID_ARG(&volume_proto->chkid), m, n, interval, a2, b2, qos->is_recovery, qos->recovery_count);

                // 判断是否进入恢复模式
                if (qos->is_recovery) {
                        if (a2 == 0 || b2 == 0) {
                                qos->recovery_count++;

                                // 连续多次，才退出调节模式
                                if (qos->recovery_count > 3) {
                                        qos->is_recovery = 0;

                                        DINFO("vol "CHKID_FORMAT" exit recovery mode, used %jd m %ju n %ju a2 %ju b2 %ju\n",
                                              CHKID_ARG(&volume_proto->chkid),
                                              interval / (1000 * 1000),
                                              m, n, a2, b2);
                                }
                        } else {
                                qos->recovery_count = 0;
                        }
                } else {
                        if (a2 != 0 && b2 != 0) {
                                qos->is_recovery = 1;

                                // _gettimeofday(&qos->range.p1.t, NULL);
                                // qos->range.p1.io.request = qos->range.p2.io.request;
                                // qos->range.p1.recovery.request = qos->range.p2.recovery.request;

                                uint64_t dft = qos_config_get_iops_dft();
                                __qos_set_iops(qos, dft);
                                qos->range.p1.dist = -1;

                                DINFO("vol "CHKID_FORMAT" enter recovery mode\n",
                                      CHKID_ARG(&volume_proto->chkid));
                        }
                }

                if (qos->is_recovery) {
                        // calc distance
                        // determine next step (token)
                        // let p1 = p2

                        uint64_t a1 = qos->range.p2.io.token;
                        uint64_t b1 = qos->range.p2.recovery.token;

                        double dist = sqrt((a1 - a2) * (a1 - a2) + (b1 - b2) * (b1 - b2));
                        qos->range.p2.dist = dist;

                        DINFO("vol "CHKID_FORMAT" p1 %ju %0.3f p2 %ju %0.3f @ io %ju %ju %ju r %ju %ju %ju scale %d\n",
                              CHKID_ARG(&volume_proto->chkid),
                              qos->range.p1.io.token,
                              qos->range.p1.dist,
                              qos->range.p2.io.token,
                              qos->range.p2.dist,
                              a1, a2, m,
                              b1, b2, n,
                              qos->scale);

                        qos_point_t pt = qos->range.p2;

                        int step = qos_config_get_iops_step();
                        if (dist <= qos_config_get_iops_dist()) {
                                __qos_change_iops(qos, +step);
                        } else {
                                if (qos->range.p1.dist >= 0) {
                                        // 按比例调节
                                        if (qos->range.p1.dist <= qos->range.p2.dist) {
                                                if (qos->range.p1.io.token < qos->range.p2.io.token) {
                                                        __qos_change_iops(qos, -step);
                                                } else {
                                                        __qos_change_iops(qos, +step);
                                                }
                                        } else {
                                                if (qos->range.p1.io.token < qos->range.p2.io.token) {
                                                        __qos_change_iops(qos, +step);
                                                } else {
                                                        __qos_change_iops(qos, -step);
                                                }
                                        }
                                } else {
                                        __qos_change_iops(qos, +step);
                                }
                        }

                        // next interval
                        qos->range.p1 = pt;
                } else {
                        // TODO if not recovery mode, p1.t == 0
                        qos->range.p1.t = qos->range.p2.t;
                }

                // update config
                int scale = recovery_load_scale(volume_proto->table1.pool);
                if (qos->scale != scale) {
                        DINFO("scale changed %d -> %d\n", qos->scale, scale);
                        qos->scale = scale;
                }
        }

        qos->recovery.enter_count++;

        ret = __qos_wait(qos, 0);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        qos->recovery.request_count++;
        qos->recovery.task_count++;

        // TODO 需要考虑权重，选择一基本单元
        qos->range.p2.recovery.request += 4;

        return 0;
err_ret:
        return ret;
}

inline int volume_proto_recovery_exit(volume_proto_t *volume_proto, const chkid_t *chkid)
{
        (void) chkid;

        qos_t *qos = &volume_proto->qos;

        qos->recovery.exit_count++;
        qos->recovery.task_count--;

        // __qos_dump(volume_proto, "recovery_exit");
        return 0;
}

static int __timerange2_func(timerange2_t *range, int64_t interval, void *context)
{
        volume_proto_t *volume_proto = context;
        qos_t *qos = &volume_proto->qos;

        __qos_dump(volume_proto, "io_enter");

        // 如长时间无恢复任务，退出恢复模式
        if (range->p2.count1 - range->p1.count1 == 0) {
                qos->is_recovery = 0;
                qos->recovery_count = 0;

                DINFO("vol "CHKID_FORMAT" exit recovery mode\n",
                      CHKID_ARG(&volume_proto->chkid));
        } else {
                DINFO("vol "CHKID_FORMAT" rps %ju iops %ju\n",
                      CHKID_ARG(&volume_proto->chkid),
                      (range->p2.count1 - range->p1.count1) * 1000 * 1000 / interval,
                      (range->p2.count2 - range->p1.count2) * 1000 * 1000 / interval);
        }

        return 0;
}

inline int volume_proto_io_enter(volume_proto_t *volume_proto, const io_t *io)
{
        int ret;
        qos_t *qos = &volume_proto->qos;

        if (qos->is_recovery) {
                timerange2_update(&qos->io_range,
                                  qos->recovery.request_count,
                                  qos->io.request_count,
                                  __timerange2_func,
                                  volume_proto);
        }

        qos->io.enter_count++;

        ret = __qos_wait(qos, 1);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        qos->io.request_count++;
        qos->io.task_count++;

        // TODO 如何确定权重？
        if (io->size >= 256 * 1024) {
                qos->range.p2.io.request += 4;
        } else {
                qos->range.p2.io.request += 1;
        }

        return 0;
err_ret:
        return ret;
}

inline int volume_proto_io_exit(volume_proto_t *volume_proto)
{
        qos_t *qos = &volume_proto->qos;

        qos->io.exit_count++;
        qos->io.task_count--;

        // __qos_dump(volume_proto, "io_exit");
        return 0;
}

#endif
